#include <stdlib.h>
#include <stdio.h>

#include "shared.h"
#include "osf.h"
#include "flit.h"
#include "flit_queue.h" 
#include "power.h" 

//#define dif1

int *switch_dly_tap;

inline void mark_ready(unsigned int chn, unsigned int node); 

void osf_init(void)
{
  int node, chn; 
  switch_dly_tap = (int *)malloc(NUM_CHN * sizeof(int));
  if(switch_dly_tap == NULL) 
  { 
    perror("Fail to allocate switch delay taps"); 
    exit(1); 
  }

  alloc_res = (int **)malloc(NUM_CHN * sizeof(int *)); 
  curr_res = (int **)malloc(NUM_CHN * sizeof(int *));
  if(!alloc_res || !curr_res) 
  {
    perror("Fail to allocate node reservations");
    exit(1); 
  }

  alloc_res[0] = (int *)malloc(NUM_CHN * NUM_NODES * sizeof(int)); 
  curr_res[0]  = (int *)malloc(NUM_CHN * NUM_NODES * sizeof(int));
  if(!alloc_res[0] || !curr_res[0]) 
  {
    perror("Fail to allocate node reservations");
    exit(1); 
  }
 
  for(chn = 0;chn < NUM_CHN;chn ++)
  { 
    switch_dly_tap[chn] = -1;
    alloc_res[chn] = alloc_res[0] + chn * NUM_NODES; 
    curr_res[chn] = curr_res[0] + chn * NUM_NODES; 
    for(node = 0;node < NUM_NODES;node ++)
    {
      if(TRA_PATTERN == TRAN)
      { 
        alloc_res[chn][node] = FRAME_SZ; //default equal bandwidth allocation
        curr_res[chn][node] = FRAME_SZ; //default equal bandwidth allocation
      }
      else 
      {   
        alloc_res[chn][node] = FRAME_SZ/NUM_NODES; //default equal bandwidth allocation
        curr_res[chn][node] = FRAME_SZ/NUM_NODES; //default equal bandwidth allocation

        /* Hardcoded differential reservations */
#ifdef dif1 //QoS dif1 
        int tmpx = node % 16; 
        int tmpy = node / 16;
        int dir  = node / 16; 
        if(dir % 2 == 0)
        {
          if(tmpx/2 <= 3 && tmpy <= 1)
          {
            alloc_res[chn][node] = (int)(FRAME_SZ * 1.5) / NUM_NODES; //default equal bandwidth allocation
            curr_res[chn][node] =  (int)(FRAME_SZ * 1.5) / NUM_NODES; //default equal bandwidth allocation
          }
          if(tmpx/2 >=4 && tmpy >= 2) 
          {
            alloc_res[chn][node] = (int)(FRAME_SZ * 0.5) / NUM_NODES; //default equal bandwidth allocation
            curr_res[chn][node] =  (int)(FRAME_SZ * 0.5) / NUM_NODES; //default equal bandwidth allocation
          }
        }
        if(dir % 2 == 1)
        {
          if(tmpx/2 >= 4 && tmpy <= 1)
          {
            alloc_res[chn][node] = (int)(FRAME_SZ * 1.5) / NUM_NODES; //default equal bandwidth allocation
            curr_res[chn][node] =  (int)(FRAME_SZ * 1.5) / NUM_NODES; //default equal bandwidth allocation
          }
          if(tmpx/2 <=3 && tmpy >= 2) 
          {
            alloc_res[chn][node] = (int)(FRAME_SZ * 0.5) / NUM_NODES; //default equal bandwidth allocation
            curr_res[chn][node] =  (int)(FRAME_SZ * 0.5) / NUM_NODES; //default equal bandwidth allocation
          }
        }
#endif
#ifdef dif2 //QoS dif2 
        int cluster = node / 4;
        int tmpy    = node / 16; 
        if((cluster) % 2 == 0)
        { 
          alloc_res[chn][node] = (int)(FRAME_SZ * 0.5) / NUM_NODES; //default equal bandwidth allocation
          curr_res[chn][node] =  (int)(FRAME_SZ * 0.5) / NUM_NODES; //default equal bandwidth allocation
        } 
        else 
        { 
          alloc_res[chn][node] = (int)(FRAME_SZ * 1.5) / NUM_NODES; //default equal bandwidth allocation
          curr_res[chn][node] =  (int)(FRAME_SZ * 1.5) / NUM_NODES; //default equal bandwidth allocation
        }
#endif
      }
    }
  }
}

void osf_stage(void)
{ 
  int node, chn, pri;
  flit_t *flit_ptr;  
 
  //1. Check whether to broadcast frame switching 
  for(chn = 0;chn < NUM_CHN;chn ++)
    if(switch_dly_tap[chn] == 0)
    { 
      switch_dly_tap[chn] = -1; //Use -1 to mark switching is done once
      for(node = 0;node < NUM_NODES;node ++)
        if(node != chn)
        {
          //Energy: 1. E/O emit one-bit frame-switch signal
          //        2. O/E detect frame-switch signal i
          //        3. tune into resonance
          dyn_o_power += PD_MOD + PD_MOD * (NUM_NODES - 1); 
          dyn_e_power += PD_DR + PD_OE * (NUM_NODES - 1);  
 
          voq_stat[node][chn] = BUSY;
          last_busy_cycle[node][chn] = SRC_CLK; 
          mark_ready(chn, node);
          if(VERBOSE)
            printf("[%d|%d|FS] all non-home nodes increment the frame\n", 
                    SRC_CLK, chn);
        }
    }


  //2. Check the channel completion status
  for(chn = 0;chn < NUM_CHN;chn ++)
   if(comp_counter[chn] == NUM_NODES - 1 && switch_dly_tap[chn] == -1) 
   { 
     comp_counter[chn] = 0;
     switch_dly_tap[chn] = CHN_FLY_TIME + SWITCH_PROC_DLY;
     if(VERBOSE)
       printf("[%d|%d|C] Receive completion signal, reset home node status\n", 
               SRC_CLK, chn); 
   }

  //3. Decrement the delay tap timer 
  for(chn = 0;chn < NUM_CHN;chn ++)
    if(switch_dly_tap[chn] > 0)
      switch_dly_tap[chn] --;

  //4. Check & update node status
  /* A node transit from BUSY to IDLE only if:
   * (curr_res == 0 && no ready flit exists) || idle for IDL_LAT cycles
   */
  for(node = 0;node < NUM_NODES; node ++) 
    for(chn = 0;chn < NUM_CHN;chn ++)
    {
      bool ready_flit_exist = false;
      if(inj_queues[node][chn].num_flits > 0)
      { 
        flit_ptr = read(&inj_queues[node][chn]);
        if(flit_ptr->type == READY)
          ready_flit_exist = true;
      }
      if(voq_stat[node][chn] == BUSY)
        if((curr_res[chn][node] == 0 && !ready_flit_exist) ||  
            SRC_CLK >= last_busy_cycle[node][chn] + IDL_LAT)
        {
          //Energy: 1. tune out of modulation
          dyn_o_power += PD_MOD;
 
          voq_stat[node][chn] = IDLE; 
          comp_counter[chn] ++; 
          if(VERBOSE)
            printf("[%d|%d|B2I] node %d becomes idle\n", 
                    SRC_CLK, node, node);
        }
    } 
}  

inline void mark_ready(unsigned int chn, unsigned int node) 
{
  flit_queue_t *q; 
  flit_t *flit_ptr;
 
  q = &inj_queues[node][chn]; 
  curr_res[chn][node] = alloc_res[chn][node];  
  for(flit_ptr = q->head;flit_ptr != NULL;flit_ptr = flit_ptr->next)
//    if(flit_ptr->type == PENDING)
//      flit_ptr->type = READY;
    if(flit_ptr->type == PENDING)
    {
      if(curr_res[chn][node] > 0) 
      {
        flit_ptr->type = READY; 
        curr_res[chn][node] --; 
      } 
    }
} 
